/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.context.service;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import org.zhiqim.httpd.HttpContext;
import org.zhiqim.httpd.HttpExecutor;
import org.zhiqim.httpd.HttpHeaderInner;
import org.zhiqim.httpd.HttpRequest;
import org.zhiqim.httpd.HttpResponse;
import org.zhiqim.httpd.context.ZmlBootstrap;
import org.zhiqim.httpd.context.ZmlContextConstants;
import org.zhiqim.httpd.context.annotation.AnIntercept;
import org.zhiqim.httpd.context.annotation.AnInterceptNot;
import org.zhiqim.httpd.context.annotation.AnLogNot;
import org.zhiqim.httpd.context.annotation.AnPathRMI;
import org.zhiqim.httpd.context.config.ZActionPackageLoader;
import org.zhiqim.httpd.context.core.Context;
import org.zhiqim.httpd.context.core.Interceptor;
import org.zhiqim.kernel.Z;
import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.annotation.AnFilterNot;
import org.zhiqim.kernel.annotation.AnGlobal;
import org.zhiqim.kernel.annotation.AnNew;
import org.zhiqim.kernel.annotation.AnTransaction;
import org.zhiqim.kernel.model.codes.MD5;
import org.zhiqim.kernel.model.objects.Int;
import org.zhiqim.kernel.service.Service;
import org.zhiqim.kernel.transaction.Transaction;
import org.zhiqim.kernel.transaction.TransactionManager;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.Classes;
import org.zhiqim.kernel.util.DateTimes;
import org.zhiqim.kernel.util.Htmls;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Urls;
import org.zhiqim.kernel.util.Validates;

/**
 * 远程方法调用，通过HTTP封装返回字符串类型值，返回值由类的方法决定
 * 1.应用，格式如201200
 * 2.时间戳，格式如2014-03-21 11:52:23
 * 3.类名，格式如org.zhiqim.Test
 * 4.方法名，格式如doTest
 * 5.签名，格式为MD5.encodeUTF8(key + timestamp + className + methodName + secret)
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 * @version v1.3.0 @author zouzhigang 2017-3-15 增加对表单方式的提交
 */
public class RmiService implements HttpExecutor, ZmlContextConstants
{
    private int timeDifference = 10 * 60 * 1000;
    private HashSet<String> innerObjs = new HashSet<>();
    private HashMap<String, Class<?>> classMap = new HashMap<String, Class<?>>();
    private HashMap<String, Object> instanceMap = new HashMap<String, Object>();
    
    public RmiService()
    {
        innerObjs.add(_CONTEXT_);
        innerObjs.add(_SESSION_);
        innerObjs.add(_SESSION_USER_);
        innerObjs.add(_LOG_);
    }
    
    @Override
    public boolean isMatch(String pathInContext)
    {
        return _PATH_SERVICE_RMI_.equals(pathInContext);
    }
    
    @Override
    public void handle(HttpRequest request, HttpResponse response) throws IOException
    {
        String mimeType = request.getMimeType();
        if (!_POST_.equals(request.getMethod()) || !_UTF_8_.equalsIgnoreCase(request.getCharacterEncodingHeader()) ||
            !(_TEXT_PLAIN_.equals(mimeType) || _APPLICATION_X_WWW_FORM_.equals(mimeType) || _APPLICATION_XML_.equals(mimeType) || _APPLICATION_JSON_.equals(mimeType)))
        {//不是POST/UTF-8和的访问禁止，不是TEXT/FORM/XML/JSON类型的不支持
            response.sendError(_403_FORBIDDEN_);
            return;
        }
        
        String serviceId = Strings.trim(request.getHeader(_X_RMI_SERVICE_), _EMPTY_);
        String clazz = Strings.trim(request.getHeader(_X_RMI_CLASS_), _EMPTY_);
        String method = request.getHeader(_X_RMI_METHOD_);
        String key = request.getHeader(_X_RMI_KEY_);
        String timestamp = request.getHeader(_X_RMI_TIMESTAMP_);
        String authorization = request.getHeader(_X_RMI_AUTHORIZATION_);
        if ((Validates.isEmptyBlank(serviceId) && Validates.isEmptyBlank(clazz)) || Validates.isEmptyBlank(method) 
            || Validates.isEmptyBlank(key) || Validates.isEmptyBlank(authorization) || !Validates.isDateTime(timestamp))
        {//基础5个参数必须填入，否则认为是非法请求
            response.sendError(_400_BAD_REQUEST_);
            return;
        }
        
        long diffTime = System.currentTimeMillis() - DateTimes.toLong(timestamp);
        if ((diffTime > 0 && diffTime > timeDifference) || (diffTime < 0 && diffTime < -timeDifference))
        {//误差只允许在10分钟以内
            response.sendError(_400_BAD_REQUEST_);
            return ;
        }
        
        HttpContext context = request.getContext();
        String secret = context.getAttributeString("rmi."+key);
        if (Validates.isEmptyBlank(secret))
        {
            response.sendError(_403_FORBIDDEN_);
            return;
        }
        
        String sign = MD5.encodeUTF8(key + timestamp + clazz + method + serviceId + secret);
        if (!authorization.equalsIgnoreCase(sign))
        {//验证签名失败
            response.sendError(_401_UNAUTHORIZED_);
            return;
        }
        
        String remoteIp = request.getRemoteAddr();
        String ipWhiteList = context.getAttributeString("rmi."+key+_SERV_IP_WHITE_LIST_SUFFIX_);
        String ipBlackList = context.getAttributeString("rmi."+key+_SERV_IP_BLACK_LIST_SUFFIX_);
        boolean ipDefaultPermission = context.getAttributeBoolean("rmi."+key+_SERV_IP_DEFAULT_PERMISSION_, true);
        if (!Validates.isIPAllowByWhiteBlackList(remoteIp, ipWhiteList, ipBlackList, ipDefaultPermission))
        {//对IP进行鉴权，支持黑白名单，开放时黑名单禁止访问，关闭时仅白名单可访问
            response.sendError(_403_FORBIDDEN_);
            return;
        }
        
        service(request, response);
    }
    
    /***
     * 正常服务提供
     * 
     * @param request       请求
     * @param response      响应
     * @throws IOException  异常
     */
    void service(HttpRequest request, HttpResponse response) throws IOException
    {
        Context context = (Context)request.getContext();
        ZmlBootstrap bootstrap = context.getBootstrap();
        
        request.setResponseNoCache();
        request.setResponseEncodingUTF8();
        response.setContentType(_TEXT_PLAIN_);
        
        //第一步，检查类名是否正确，先判断别名，再判断是否已处理，最后forName
        String serviceId = Strings.trim(request.getHeader(_X_RMI_SERVICE_), "");
        String className = Strings.trim(request.getHeader(_X_RMI_CLASS_), "");
        String classNameId = Validates.isEmpty(className)?serviceId:className;
        
        Class<?> curClass = getClass(request, serviceId, className);
        if (curClass == null)
        {
            if (Validates.isEmpty(serviceId))
            {
                bootstrap.log(request, "["+className+"类名和别名不存在]");
                response.sendError(_412_PRECONDITION_FAILED_, "["+className+"]类名和别名不存在");
            }
            else
            {
                bootstrap.log(request, "[未找到"+serviceId+"对应的服务]");
                response.sendError(_412_PRECONDITION_FAILED_, "[未找到"+serviceId+"对应的服务]");
            }
            return;
        }
        
        //第二步，检查参数对应的方法，匹配成功得到参数列表
        String methodName = request.getHeader(_X_RMI_METHOD_);
        
        List<Object> paramList = new ArrayList<Object>();
        Method curMethod = getMethod(request, response, curClass, paramList);
        if (curMethod == null)
        {
            bootstrap.log(request, "["+classNameId+"]中的["+methodName+"]未找到或参数不匹配");
            response.sendError(_412_PRECONDITION_FAILED_, "["+methodName+"]未找到或参数不匹配");
            return;
        }
        
        AnLogNot logNot = curMethod.getAnnotation(AnLogNot.class);
        if (logNot == null)
        {//以方法内配置为主，方法中没有，取类配置
            logNot = curClass.getAnnotation(AnLogNot.class);
        }
        
        AnPathRMI pathInRMI = curMethod.getAnnotation(AnPathRMI.class);
        if (pathInRMI == null)
        {//以方法内配置为主，方法中没有，取类配置
            pathInRMI = curClass.getAnnotation(AnPathRMI.class);
        }
        
        if (pathInRMI != null)
        {//有配置远程方法调用的路径的，设置到请求中
            ((HttpHeaderInner)request).setPathInRMI(pathInRMI.value());
        }
        
        //第三步，检查拦截器，如果有拦截器要先处理
        AnIntercept classInterceptor = curClass.getAnnotation(AnIntercept.class);
        AnIntercept methodInterceptor = curMethod.getAnnotation(AnIntercept.class);
        AnInterceptNot methodInterceptorNot = curMethod.getAnnotation(AnInterceptNot.class);
        String interceptors = ZActionPackageLoader.getInterceptor(classInterceptor, methodInterceptor, methodInterceptorNot);
        if (Validates.isNotEmpty(interceptors))
        {
            if (!chkInterceptor(request, response, interceptors, classNameId, methodName, logNot))
            {//拦截时，有错误或认证失败不再继续
                return;
            }
        }
        
        //第四步，对参数进行安全过滤，如果未指定，则要求安全过滤
        Object[] curParams = paramList.toArray();
        if (!curMethod.isAnnotationPresent(AnFilterNot.class))
        {
            for (int i=0;i<curParams.length;i++)
            {
                if (curParams[i] instanceof String){
                    curParams[i] = Htmls.filterAll((String)curParams[i]);
                }
            }
        }
        
        //第五步，检查事务定义
        boolean hasTx = false, allTx = false;
        List<String> txIdList = new ArrayList<>();
        AnTransaction classTx = curClass.getAnnotation(AnTransaction.class);
        
        if (classTx != null)
        {
            hasTx = true;
            if (Validates.isEmpty(classTx.value()))
                allTx = true;
            else
            {
                for (String id : classTx.value())
                    txIdList.add(id);
            }
        }

        if (!allTx)
        {//如果不是所有的则再检查方法中的定义
            AnTransaction methodTx = curMethod.getAnnotation(AnTransaction.class);
            if (methodTx != null)
            {
                hasTx = true;
                if (Validates.isEmpty(methodTx.value()))
                    allTx = true;
                else
                {
                    for (String id : methodTx.value())
                        txIdList.add(id);
                }
            }
        }
        
        Object result = null;
        Transaction tx = null;
        
        try
        {
            if (hasTx)
            {//定义事务则开始
                String[] ids = allTx?new String[0]:Arrays.toFilterSameList(txIdList);
                tx = TransactionManager.beginTransaction(ids);
            }
            
            //第六步，调用类方法，得到结果
            curMethod.setAccessible(true);
            if (Modifier.isStatic(curMethod.getModifiers()))
            {//6.1优先静态方法
                result = curMethod.invoke(null, curParams);
            }
            else if (Validates.isNotEmpty(serviceId) && Z.serv().has(serviceId))
            {//6.2其次判断是否是服务
                Object instance = Z.serv().get(serviceId);
                result = curMethod.invoke(instance, curParams);
            }
            else if (innerObjs.contains(className))
            {//6.3再次内部对象，取其实例
                Object instance = getInnerObject(request, className);
                result = curMethod.invoke(instance, curParams);
            }
            else if (context.hasActionInstance(className))
            {//6.4再次类是Action，取其实例
                Object instance = context.getActionInstance(className);
                result = curMethod.invoke(instance, curParams);
            }
            else if (curClass.isAnnotationPresent(AnNew.class))
            {//6.5然后判断是否指定每次都新建该类，保持属性为原始数据
                Object instance = curClass.newInstance();
                result = curMethod.invoke(instance, curParams);
            }
            else if (curClass.isAnnotationPresent(AnGlobal.class))
            {//6.6再然后，如果定义在AnGlobal中，取全局变量
                Object instance = Z.glb().getc(curClass);
                result = curMethod.invoke(instance, curParams);
            }
            else
            {//6.7最后默认采用单例，如果属性被修改，后续调用为修改后数据
                
                Object instance = instanceMap.get(className);
                if (instance == null)
                {
                    instance = curClass.newInstance();
                    instanceMap.put(className, instance);
                }
                
                result = curMethod.invoke(instance, curParams);
            }

            if (tx != null)
            {//事务提交
                try{tx.commit();}catch (Exception e){bootstrap.log(request, "["+classNameId+"]["+methodName+"][tx.commit()异常]", e);throw new Exception();}
            }
            
            if (response.isCommitted())
            {
                bootstrap.log(request, "["+classNameId+"]["+methodName+"][Commited]");
                return;
            }
        }
        catch(Exception e)
        {//6.8.调用类方法异常
            
            if (tx != null)
            {//事务回滚
                try{tx.rollback();}catch (Exception e1){bootstrap.log(request, "["+className+"]["+methodName+"][tx.rollback异常]", e1);}
            }
            
            if (response.isCommitted())
            {
                bootstrap.log(request, "["+classNameId+"]["+methodName+"][Commited]");
                return;
            }
            
            String error = e.getMessage();
            if (e.getCause() != null)
            {//反射调用时，多了一级InvokeTargetException
                error = e.getCause().getMessage();
                if (error == null)
                    error = "未知错误["+e.getCause().getClass().getName()+"]";
            }
            
            if (error == null)
            {//为空最后统一显示未知错误
                error = "未知错误";
            }
            
            bootstrap.log(request, "["+classNameId+"]["+methodName+"]调用异常", e);
            response.sendError(_500_INTERNAL_SERVER_ERROR_, error);
            return;
        }
        finally
        {
            if (tx != null)
            {//事务关闭
                try{tx.close();}catch (Exception e){bootstrap.log(request, "["+classNameId+"]["+methodName+"][tx.close异常]", e);}
            }
        }
        
        //第七步，返回结果，两种方法的支持，1)方法返回结果ret，2)结果放置到属性中返回
        int status = request.getResponseStatus();
        String text = request.getResponseText();
        
        if (void.class == curMethod.getReturnType())
        {//无返回值时结果取text
            result = text;
        }
        else if (Int.class == curMethod.getReturnType())
        {//返回值为Int时要拆开
            Int ret = (Int)result;
            status = ret.code();
            result = ret.msg();
        }
        //else{其他类型取toString}
        
        if (result == null)
            result = _EMPTY_;
        
        if (status == 0)
        {
            if (logNot == null)
            {//支持日志
                bootstrap.log(request, "["+classNameId+"]["+methodName+"]");
            }
            
            response.sendContent(200, result.toString());
        }
        else
        {
            if (logNot == null)
            {//支持日志
                bootstrap.log(request, "["+classNameId+"]["+methodName+"]["+status+"]");
            }
            
            if (status >= _601_REDIRECT_PARENT_ && status <= _603_REDIRECT_TOP_)
                response.sendContent(status, result.toString());//重定向
            else
                response.sendError(status, result.toString());
        }
    }
    
    /**
     * 检查拦截器
     * 
     * @param request           请求
     * @param response          响应
     * @param interceptors      拦截器值
     * @return                  是否成功，=true表示成功，=false表示失败，退出
     * @throws IOException      异常
     */
    public boolean chkInterceptor(HttpRequest request, HttpResponse response, String interceptors, String classNameId, String methodName, AnLogNot logNot) throws IOException
    {
        if (Validates.isEmpty(interceptors))
            return true;
        
        Context context = (Context)request.getContext();
        ZmlBootstrap bootstrap = context.getBootstrap();
        
        List<Interceptor> interceptorList = context.getInterceptorList(interceptors);
        for (Interceptor interceptor : interceptorList)
        {
            if (interceptor == null)
            {
                if (logNot == null)
                    bootstrap.log(request, "["+classNameId+"]["+methodName+"][拦截器("+interceptors+")]某个不存在，请管理员检查配置");
                response.sendError(_621_INTERCEPTOR_NOT_EXIST_, "[拦截器("+interceptors+")]某个不存在，请管理员检查配置");
                return false;
            }
            
            try
            {
                interceptor.intercept(request);
                if (response.isCommitted())
                {
                    if (logNot == null)
                        bootstrap.log(request, "["+classNameId+"]["+methodName+"][拦截器("+interceptor+")]直接处理结果");
                    return false;
                }
                
                if (!request.isResponseSuccess())
                {
                    int status = request.getResponseStatus();
                    String text = request.getResponseText();
                    response.sendError(status, text);
                    
                    if (logNot == null)
                        bootstrap.log(request, "["+classNameId+"]["+methodName+"][拦截器("+interceptor+")]拦截"+(request.isResponseRedirect()?"并重定向":""));
                    return false;
                }
            }
            catch(Exception e)
            {
                if (logNot == null)
                    bootstrap.log(request, "["+classNameId+"]["+methodName+"][拦截器("+interceptor+")]调用异常,"+e.getMessage(), e);
                response.sendError(_623_INTERCEPTOR_EXCEPTION_, e.getMessage()==null?"未知错误":e.getMessage());
                return false;
            }
        }
        
        return true;
    }
    
    
    /** 获取类结构 */
    private Class<?> getClass(HttpRequest request, String serviceId, String className)
    {
        if (Validates.isNotEmpty(serviceId))
        {//1.如果有服务ID，以服务ID为准
            Service service = Z.serv().get(serviceId);
            if (service == null)
                return null;
            
            //如果类名也提供，则再比较类名也必须相等
            Class<?> clazz = service.getClass();
            if (Validates.isEmpty(className) || className.equals(clazz.getName()))
                return clazz;
            else
                return null;
        }
        
        //2.优先类别名
        Class<?> clazz = Z.cls().get(className);
        if (clazz != null)
            return clazz;
        
        //3.再判断内置类
        clazz = classMap.get(className);
        if (clazz != null)
            return clazz;
        
        clazz = getInnerClass(request, className);
        if (clazz != null)
            return clazz;
        
        clazz = Classes.forName(className);
        if (clazz == null)
            return null;
        
        classMap.put(className, clazz);
        return clazz;
    }
    
    /** 获取方法 */
    private Method getMethod(HttpRequest request, HttpResponse response, Class<?> curClass, List<Object> curParams)
    {
        String methodName = request.getHeader(_X_RMI_METHOD_);
        List<String> paramList = parseParameters(request);
        
        Method[] mtds = curClass.getMethods();
        for (Method method : mtds)
        {
            //2.1.判断方法名或别名是否相同，方向名不同结束本次查找
            AnAlias alias = method.getAnnotation(AnAlias.class);
            if (!(methodName.equals(method.getName()) || (alias != null && methodName.equals(alias.value()))))
                continue;
            
            //2.2.判断参数个数是否相同，把形参为HttpRequest和HttpResponse的填充实参request和response，并记录数目
            Class<?>[] paramTypes = method.getParameterTypes();
            int reqRespNum = 0;
            for (Class<?> clazz : paramTypes)
            {
                if (clazz == HttpRequest.class || clazz == HttpResponse.class)
                    reqRespNum++;
            }
            
            //2.3.判断参数个数是否相等，个数不相等结束本次查找
            if (paramTypes.length - reqRespNum  != paramList.size())
                continue;

            //2.4.判断参数类型是否匹配成功
            boolean isMatch = true;
            Object[] paramValues = new Object[paramTypes.length];
            for (int i=0, ind=0;i<paramTypes.length;i++)
            {
                if (paramTypes[i] == HttpRequest.class)
                    paramValues[i] = request;
                else if (paramTypes[i] == HttpResponse.class)
                    paramValues[i] = response;
                else
                {
                    Object param = toObject(paramTypes[i], paramList.get(ind));
                    if (param == null)
                    {
                        isMatch = false;
                        break;
                    }
                    
                    paramValues[i] = param;
                    ind++;//由于参数可能需要request,所以个数不同，由ind按顺序对照
                }
            }
            
            //2.5.匹配成功，则表示找到对应的，如果匹配不成功，则继续查找。
            if (isMatch)
            {
                for (Object obj : paramValues)
                    curParams.add(obj);
                
                return method;
            }
        }
        
        return null;
    }
    
    /** 从请求中读取所有参数转化为列表 */
    private List<String> parseParameters(HttpRequest request)
    {
        if (!request.isMimeTextPlain())
        {//不是文本的不解析内容中的参数
            return new ArrayList<String>();
        }
        
        String parameters = request.getInputStreamString();
        if (Validates.isEmptyBlank(parameters))
            return new ArrayList<String>();
        
        parameters = Urls.decodeUTF8(parameters);
        List<String> paramList = new ArrayList<String>();
        for (int i=0,start=0,end=parameters.indexOf('#');end!=-1;i++,start=end,end=parameters.indexOf('#', start+1))
        {
            if (i % 2 == 0)
                continue;
            String param = parameters.substring(start + 1, end);
            if (param.indexOf("-%2-%-3%-") != -1)
                param = param.replaceAll("-%2-%-3%-", "#");
            paramList.add(param);
        }
        
        return paramList;
    }
    
    /**********************************************************************************/
    //支持的四个内部对象
    /**********************************************************************************/
    
    private Class<?> getInnerClass(HttpRequest request, String className)
    {
        if (!innerObjs.contains(className))
            return null;
        
        if (_CONTEXT_.equals(className))
            return request.getContext().getClass();
        else if (_LOG_.equals(className))
            return request.getLog().getClass();
        else if (_SESSION_.equals(className))
            return request.getSession() == null?null:request.getSession().getClass();
        else
            return request.getSessionUser() == null?null:request.getSessionUser().getClass();
    }
    
    private Object getInnerObject(HttpRequest request, String className)
    {
        if (!innerObjs.contains(className))
            return null;
        
        if (_CONTEXT_.equals(className))
            return request.getContext();
        else if (_LOG_.equals(className))
            return request.getLog();
        else if (_SESSION_.equals(className))
            return request.getSession();
        else
            return request.getSessionUser();
    }
    
    /**
     * 把字符串，转化成可能的8种基本类型对象和字符串对象，转化失败其不是基本类型返回null
     * 
     * @param clazz 转化的类型
     * @param s     字符串
     * @return 如果转化成功，返回该类型的值，否则返回null;
     */
    public static Object toObject(Class<?> clazz, String s)
    {
        if (s == null)
        {//无需转化
            return null;
        }
        
        if (clazz == String.class)
        {//字符串的支持空白字符
            return s;
        }
        
        //基本类型去除空白字符
        s = Strings.trim(s);
        
        try
        {
            if (clazz == int.class || clazz == Integer.class)
                return Integer.valueOf(s);
            else if (clazz == long.class || clazz == Long.class)
                return Long.valueOf(s);
            else if (clazz == short.class || clazz == Short.class)
                return Short.valueOf(s);
            else if (clazz == char.class || clazz == Character.class)
                return Character.valueOf(s.charAt(0));
            else if (clazz == byte.class || clazz == Byte.class)
                return Byte.valueOf(s);
            else if (clazz == float.class || clazz == Float.class)
                return Float.valueOf(s);
            else if (clazz == double.class || clazz == Double.class)
                return Double.valueOf(s);
            else if (clazz == boolean.class || clazz == Boolean.class)
                return Boolean.valueOf(s);
        }
        catch(Exception e)
        {
            return null;
        }
        
        //非基本类型和字符串不支持
        return null;
    }
}
